昨天提到JS的原型到底是什麽,以及一些內建的Object原型方法,今天來講一下建構函式。
In JavaScript, all functions have a property named prototype.
所有類型的函式都有內建prototype
的property,而這個的property本身是一個物件,需要透過這個物件擴充需要的屬性或方法。
讓我們實際用程式碼走一次流程:
//新建一個基本函式
function contestant(){}
//使用函式叫做`prototype`的property
console.log(contestant.prototype)
結果如下:
會發現預設的屬性裡面有一個建構子,建構子的內容指向函式本身。
function Contestant(){}
console.log(Contestant.prototype.constructor)//[Function: Contestant]
同理上面的內容,我們使用一般建構物件的函式,也會有一樣的結果:
當我們使用函式當作建構函式的時候,這個屬性(在函式內建叫prototype
的屬性)就會變成新建構的物件原型。
function Contestant(id,name){
this.contestantId = id
this.contestantName = name
}
const contestant1 = new Contestant(1,'Alice')
console.log(contestant1)//Contestant {contestantId: 1, contestantName: 'Alice'}
試著在prototype裡新增方法,能讓新建構的實例被使用:
function Contestant(id,name){
this.contestantId = id
this.contestantName = name
}
Contestant.prototype.sayName = function(){
return `My name is ${this.contestantName}`
}
const contestant1 = new Contestant(1,'Alice')
console.log(contestant1.sayName())//My name is Alice
當我們讓方法存在建構函式的prototype裡,就能讓一直接下來創造的實例共享這個方法。
上面提到function本身都有一個原型,是為了說明主題能集中在建構函式的原型。但這裡想另外提到,只要物件本身就有自己的原型,而Javascript本身有為了內建物件去設置原型方法。
就目前理解來說,有[[prototype]]的是:物件型別的變數、有建構子的內建物件,但函式的[[prototype]]是叫prototype的屬性。
prototype | |
---|---|
原始型別 | x |
物件型別 | O |
內建物件建構的實例 | O |
至於原始型別的變數,MDN上是這樣解釋:
Primitives have no methods but still behave as if they do. When properties are accessed on primitives, JavaScript auto-boxes the value into a wrapper object and accesses the property on that object instead.
這些原始型別沒有任何方法,但當我們想對這些原始型別的值使用方法時,JavaScript會把值包裝在對應的物件裡,並讓我們能夠使用該物件的方法。這個轉換過程的名稱為auto-box
或auto-boxing
。
現在我們知道只要是物件型別都有原型,且這些個別的型別都會導向物件型別。比方說Array的[[prototype]]是Array.prototype
,而Array.prototype
會導向Object.prototype
,這應該也是為什麽Array可以使用Object方法的原因,而Object.prototype
會是整個物件原型鍊中最上層的prototype。
在先前也有提到會建立預設方法,而不管是哪一種方式建立,同個型別會共享同個原生原型:
首先我創一個物件,讓名為e
的原型去新增一個method。
就會發現這樣等同於為Object.prototype新增一個method。
之前有聊到我們可以為物件修改它的原型,也能正常使用Object.prototype
的方法,但假設物件型別跟繼承過來的原型物件不一樣的話,會導致物件沒辦法使用他的原生方法。
現在個別使用型別當名稱,分別宣告一個陣列跟一個物件
確認array
的[[prototype]]
是Array.prototype
把array的prototype改成object,就會發現array的prototype沒有任何預設的Array.prototype的method可以用
上面後續如果不把array原型改回來,而是繼承另一個array的話:
先宣告另一個陣列叫array1
讓array
繼承array1
可正常使用array的method
即使array
的原型是array1
,還是可以從array1
的[[prototype]]繼承方法。
關於這些原生內建的方法,如果常常逛MDN就會發現有些方法名稱會是:Object.prototype.isPrototypeOf()
、Array.prototype.forEach()
、Array.prototype.includes()
。
我們拿其中一個方法改造一下:
Array.prototype.forEach = function(){
return 1
}
const array = [1,2,3];
array.forEach(value=>{
console.log(value)//空的
})
console.log(array.forEach());//1
source:https://memes.tw/wtf?contest=1618
沒錯,原生的方法是可以被蓋掉的,~~也太過自由,~~會導致這些方法不正常。所以使用上是合法的,但最好、最好、最好不要這樣做。
new
是一個運算子,有兩個情況需要使用:
也就是說,new
必須搭配有內建constructor
的物件一起使用。
關於第一點,來回顧剛剛的程式碼範例:自訂這個函式的內容,並透過new
建立實例。
function Contestant(id,name){
this.contestantId = id
this.contestantName = name
}
const contestant1 = new Contestant(1,'Alice')
console.log(contestant1)//Contestant {contestantId: 1, contestantName: 'Alice'}
第二點的話,可以參閱MDN的內建物件,這裡舉例幾個:new Array()
new Date()
new Boolean()
而且當這些建構函式要建構實例時,有些函式不需要使用new
關鍵字也可以被創立:
const arrayItem1 = Array(1,2,3)
const arrayItem2 = new Array(1,2,3)
console.log(arrayItem1);//[ 1, 2, 3 ]
console.log(arrayItem2);//[ 1, 2, 3 ]
當我們在使用對建構函式使用new
:
JavaScript User-defined Object Type
該來理解 JavaScript 的原型鍊了
重新認識 JavaScript: Day 25 原型與繼承
JS 原力覺醒 Day22 - 原型共享與原型繼承
Types of JavaScript Objects: Built-in vs User-defined
書
忍者:JavaScript開發技巧探秘第二版(第七章)
JavaScript大全(第六版)
MDN